//  SMPSO.java
//
//  Author:
//       Antonio J. Nebro <antonio@lcc.uma.es>
//       Juan J. Durillo <durillo@lcc.uma.es>
//
//  Copyright (c) 2011 Antonio J. Nebro, Juan J. Durillo
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
// 
//  You should have received a copy of the GNU Lesser General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

package hidra.metaheuristics.smpso;

import hidra.core.population.HIDRAPopulationAlgorithm;
import hidra.gui.PlotManager;
import hidra.jmetal.core.Operator;
import hidra.jmetal.core.Problem;
import hidra.jmetal.core.Solution;
import hidra.jmetal.core.SolutionSet;
import hidra.qualityIndicator.Hypervolume;
import hidra.qualityIndicator.QualityIndicator;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Comparator;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jfree.chart.ChartPanel;

import jmetal.util.Distance;
import jmetal.util.JMException;
import jmetal.util.PseudoRandom;
import jmetal.util.archive.CrowdingArchive;
import jmetal.util.comparators.CrowdingDistanceComparator;
import jmetal.util.comparators.DominanceComparator;
import jmetal.util.wrapper.XReal;

/**
 * This class implements the SMPSO algorithm described in:
 * A.J. Nebro, J.J. Durillo, J. Garcia-Nieto, C.A. Coello Coello, F. Luna and E. Alba
 * "SMPSO: A New PSO-based Metaheuristic for Multi-objective Optimization". 
 * IEEE Symposium on Computational Intelligence in Multicriteria Decision-Making 
 * (MCDM 2009), pp: 66-73. March 2009
 */
public class SMPSO extends HIDRAPopulationAlgorithm {

	/**
	 * Stores the number of particles_ used
	 */
	private int swarmSize_;
	/**
	 * Stores the maximum size for the archive
	 */
	private int archiveSize_;
	/**
	 * Stores the maximum number of iteration_
	 */
	// private int maxIterations_;
	/**
	 * Stores the current number of iteration_
	 */
	//private int iteration_;
	/**
	 * Stores the particles
	 */
	private SolutionSet particles_;
	/**
	 * Stores the best_ solutions founds so far for each particles
	 */
	private Solution[] best_;
	/**
	 * Stores the leaders_
	 */
	private CrowdingArchive leaders_;
	/**
	 * Stores the speed_ of each particle
	 */
	private double[][] speed_;
	/**
	 * Stores a comparator for checking dominance
	 */
	private Comparator dominance_;
	/**
	 * Stores a comparator for crowding checking
	 */
	private Comparator crowdingDistanceComparator_;
	/**
	 * Stores a <code>Distance</code> object
	 */
	private Distance distance_;
	/**
	 * Stores a operator for non uniform mutations
	 */
	private Operator polynomialMutation_;

	QualityIndicator indicators_; // QualityIndicator object

	double r1Max_;
	double r1Min_;
	double r2Max_;
	double r2Min_;
	double C1Max_;
	double C1Min_;
	double C2Max_;
	double C2Min_;
	double WMax_;
	double WMin_;
	double ChVel1_;
	double ChVel2_;

	private double trueHypervolume_;
	private Hypervolume hy_;
	private SolutionSet trueFront_;
	private double deltaMax_[];
	private double deltaMin_[];
	boolean success_;
	
	private PlotManager plot_manager;

	/** 
	 * Constructor
	 * @param problem Problem to solve
	 */
	public SMPSO(Problem problem) {
		super(problem) ;

		this.plot_manager = new PlotManager();
		this.plot_manager.setupPlotExternalArchive();
		this.plot_manager.setupPlotSwarm();
		
		r1Max_ = 1.0;
		r1Min_ = 0.0;
		r2Max_ = 1.0;
		r2Min_ = 0.0;
		C1Max_ = 2.5;
		C1Min_ = 1.5;
		C2Max_ = 2.5;
		C2Min_ = 1.5;
		WMax_ = 0.1;
		WMin_ = 0.1;
		ChVel1_ = -1;
		ChVel2_ = -1;
	} // Constructor

	public SMPSO(Problem problem,
			Vector<Double> variables,
			String trueParetoFront) throws FileNotFoundException {
		super(problem) ;
		
		this.plot_manager = new PlotManager();
		this.plot_manager.setupPlotExternalArchive();
		this.plot_manager.setupPlotSwarm();

		r1Max_ = variables.get(0);
		r1Min_ = variables.get(1);
		r2Max_ = variables.get(2);
		r2Min_ = variables.get(3);
		C1Max_ = variables.get(4);
		C1Min_ = variables.get(5);
		C2Max_ = variables.get(6);
		C2Min_ = variables.get(7);
		WMax_ = variables.get(8);
		WMin_ = variables.get(9);
		ChVel1_ = variables.get(10);
		ChVel2_ = variables.get(11);

		hy_ = new Hypervolume();
		hidra.qualityIndicator.util.MetricsUtil mu = new hidra.qualityIndicator.util.MetricsUtil();
		trueFront_ = mu.readNonDominatedSolutionSet(trueParetoFront);
		trueHypervolume_ = hy_.hypervolume(trueFront_.writeObjectivesToMatrix(),
				trueFront_.writeObjectivesToMatrix(),
				problem_.getNumberOfObjectives());

	} // SMPSO


	/** 
	 * Constructor
	 * @param problem Problem to solve
	 */
	public SMPSO(Problem problem, String trueParetoFront) throws FileNotFoundException {
		super(problem) ;
		hy_ = new Hypervolume();
		hidra.qualityIndicator.util.MetricsUtil mu = new hidra.qualityIndicator.util.MetricsUtil();
		trueFront_ = mu.readNonDominatedSolutionSet(trueParetoFront);
		trueHypervolume_ = hy_.hypervolume(trueFront_.writeObjectivesToMatrix(),
				trueFront_.writeObjectivesToMatrix(),
				problem_.getNumberOfObjectives());
		
		this.plot_manager = new PlotManager();
		this.plot_manager.setupPlotExternalArchive();
		this.plot_manager.setupPlotSwarm();

		// Default configuration
		r1Max_ = 1.0;
		r1Min_ = 0.0;
		r2Max_ = 1.0;
		r2Min_ = 0.0;
		C1Max_ = 2.5;
		C1Min_ = 1.5;
		C2Max_ = 2.5;
		C2Min_ = 1.5;
		WMax_ = 0.1;
		WMin_ = 0.1;
		ChVel1_ = -1;
		ChVel2_ = -1;
	} // Constructor

	/**
	 * Initialize all parameter of the algorithm
	 */
	public void initParams() {
		swarmSize_ = ((Integer) getInputParameter("swarmSize")).intValue();
		archiveSize_ = ((Integer) getInputParameter("archiveSize")).intValue();
		maxIterations_ = ((Integer) getInputParameter("maxIterations")).intValue();

		indicators_ = (QualityIndicator) getInputParameter("indicators");

		polynomialMutation_ = operators_.get("mutation") ; 

		iteration_ = 0 ;

		success_ = false;

		particles_ = new SolutionSet(swarmSize_);
		best_ = new Solution[swarmSize_];
		leaders_ = new CrowdingArchive(archiveSize_, problem_.getNumberOfObjectives());

		// Create comparators for dominance and crowding distance
		dominance_ = new DominanceComparator();
		crowdingDistanceComparator_ = new CrowdingDistanceComparator();
		distance_ = new Distance();

		// Create the speed_ vector
		speed_ = new double[swarmSize_][problem_.getNumberOfVariables()];


		deltaMax_ = new double[problem_.getNumberOfVariables()];
		deltaMin_ = new double[problem_.getNumberOfVariables()];
		for (int i = 0; i < problem_.getNumberOfVariables(); i++) {
			deltaMax_[i] = (problem_.getUpperLimit(i) -
					problem_.getLowerLimit(i)) / 2.0;
			deltaMin_[i] = -deltaMax_[i];
		} // for
	} // initParams 

	// Adaptive inertia 
	private double inertiaWeight(int iter, int miter, double wma, double wmin) {
		return wma; // - (((wma-wmin)*(double)iter)/(double)miter);
	} // inertiaWeight

	// constriction coefficient (M. Clerc)
	private double constrictionCoefficient(double c1, double c2) {
		double rho = c1 + c2;
		//rho = 1.0 ;
		if (rho <= 4) {
			return 1.0;
		} else {
			return 2 / (2 - rho - Math.sqrt(Math.pow(rho, 2.0) - 4.0 * rho));
		}
	} // constrictionCoefficient


	// velocity bounds
	private double velocityConstriction(double v, double[] deltaMax,
			double[] deltaMin, int variableIndex,
			int particleIndex) throws IOException {


		double result;

		double dmax = deltaMax[variableIndex];
		double dmin = deltaMin[variableIndex];

		result = v;

		if (v > dmax) {
			result = dmax;
		}

		if (v < dmin) {
			result = dmin;
		}

		return result;
	} // velocityConstriction

	/**
	 * Update the speed of each particle
	 * @throws JMException 
	 */
	private void computeSpeed(int iter, int miter) throws JMException, IOException {
		double r1, r2, W, C1, C2;
		double wmax, wmin, deltaMax, deltaMin;
		XReal bestGlobal;

		for (int i = 0; i < swarmSize_; i++) {
			XReal particle = new XReal(particles_.get(i)) ;
			XReal bestParticle = new XReal(best_[i]) ;

			//Select a global best_ for calculate the speed of particle i, bestGlobal
			Solution one, two;
			int pos1 = PseudoRandom.randInt(0, leaders_.size() - 1);
			int pos2 = PseudoRandom.randInt(0, leaders_.size() - 1);
			one = leaders_.get(pos1);
			two = leaders_.get(pos2);

			if (crowdingDistanceComparator_.compare(one, two) < 1) {
				bestGlobal = new XReal(one);
			} else {
				bestGlobal = new XReal(two);
				//Params for velocity equation
			}
			r1 = PseudoRandom.randDouble(r1Min_, r1Max_);
			r2 = PseudoRandom.randDouble(r2Min_, r2Max_);
			C1 = PseudoRandom.randDouble(C1Min_, C1Max_);
			C2 = PseudoRandom.randDouble(C2Min_, C2Max_);
			W = PseudoRandom.randDouble(WMin_, WMax_);
			//
			wmax = WMax_;
			wmin = WMin_;

			for (int var = 0; var < particle.getNumberOfDecisionVariables(); var++) {
				//Computing the velocity of this particle 
				speed_[i][var] = velocityConstriction(constrictionCoefficient(C1, C2) *
						(inertiaWeight(iter, miter, wmax, wmin) *
								speed_[i][var] +
								C1 * r1 * (bestParticle.getValue(var) -
										particle.getValue(var)) +
										C2 * r2 * (bestGlobal.getValue(var) -
												particle.getValue(var))), deltaMax_, //[var],
												deltaMin_, //[var], 
												var,
												i);
			}
		}
	} // computeSpeed

	/**
	 * Update the position of each particle
	 * @throws JMException 
	 */
	private void computeNewPositions() throws JMException {
		for (int i = 0; i < swarmSize_; i++) {
			XReal particle = new XReal(particles_.get(i)) ;
			for (int var = 0; var < particle.getNumberOfDecisionVariables(); var++) {
				particle.setValue(var, particle.getValue(var) +  speed_[i][var]) ;

				if (particle.getValue(var) < problem_.getLowerLimit(var)) {
					particle.setValue(var, problem_.getLowerLimit(var));
					speed_[i][var] = speed_[i][var] * ChVel1_; //    
				}
				if (particle.getValue(var) > problem_.getUpperLimit(var)) {
					particle.setValue(var, problem_.getUpperLimit(var));
					speed_[i][var] = speed_[i][var] * ChVel2_; //   
				}
			}
		}
	} // computeNewPositions

	/**
	 * Apply a mutation operator to some particles in the swarm
	 * @throws JMException 
	 */
	private void mopsoMutation(int actualIteration, int totalIterations) throws JMException {
		for (int i = 0; i < particles_.size(); i++) {
			if ( (i % 6) == 0)
				polynomialMutation_.execute(particles_.get(i)) ;
			//if (i % 3 == 0) { //particles_ mutated with a non-uniform mutation %3
			//  nonUniformMutation_.execute(particles_.get(i));
			//} else if (i % 3 == 1) { //particles_ mutated with a uniform mutation operator
			//  uniformMutation_.execute(particles_.get(i));
			//} else //particles_ without mutation
			//;
		}
	} // mopsoMutation

	@Override
	protected SolutionSet getParetoFront() {
		// TODO Auto-generated method stub
		return this.leaders_;
	}

	@Override
	protected SolutionSet initializationAlgorithm() throws ClassNotFoundException,
	JMException {

		initParams();

		success_ = false;
		//->Step 1 (and 3) Create the initial population and evaluate
		for (int i = 0; i < swarmSize_; i++) {
			Solution particle = new Solution(problem_);
			problem_.evaluate(particle);
			problem_.evaluateConstraints(particle);
			particles_.add(particle);
		}

		//-> Step2. Initialize the speed_ of each particle to 0
		for (int i = 0; i < swarmSize_; i++) {
			for (int j = 0; j < problem_.getNumberOfVariables(); j++) {
				speed_[i][j] = 0.0;
			}
		}

		// Step4 and 5   
		for (int i = 0; i < particles_.size(); i++) {
			Solution particle = new Solution(particles_.get(i));
			leaders_.add(particle);
		}

		//-> Step 6. Initialize the memory of each particle
		for (int i = 0; i < particles_.size(); i++) {
			Solution particle = new Solution(particles_.get(i));
			best_[i] = particle;
		}

		//Crowding the leaders_
		distance_.crowdingDistanceAssignment(leaders_, problem_.getNumberOfObjectives());

		return this.leaders_;
	}

	@Override
	protected SolutionSet runIteration() throws JMException {
		try {
			//Compute the speed_
			computeSpeed(iteration_, maxIterations_);
		} catch (IOException ex) {
			Logger.getLogger(SMPSO.class.getName()).log(Level.SEVERE, null, ex);
		}

		//Compute the new positions for the particles_            
		computeNewPositions();

		//Mutate the particles_          
		mopsoMutation(iteration_, maxIterations_);

		//Evaluate the new particles_ in new positions
		for (int i = 0; i < particles_.size(); i++) {
			Solution particle = particles_.get(i);
			problem_.evaluate(particle);
		}

		//Actualize the archive          
		for (int i = 0; i < particles_.size(); i++) {
			Solution particle = new Solution(particles_.get(i));
			leaders_.add(particle);
		}

		//Actualize the memory of this particle
		for (int i = 0; i < particles_.size(); i++) {
			int flag = dominance_.compare(particles_.get(i), best_[i]);
			if (flag != 1) { // the new particle is best_ than the older remeber        
				Solution particle = new Solution(particles_.get(i));
				best_[i] = particle;
			}
		}

		//Assign crowding distance to the leaders_
		distance_.crowdingDistanceAssignment(leaders_,
				problem_.getNumberOfObjectives());
		iteration_++;
		
		/*
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		plot_manager.plotExternalArchive(this.leaders_, iteration_ + 1);
		plot_manager.plotSwarm(this.particles_, iteration_ + 1);
		*/

		return this.leaders_;

	}

	/** 
	 * Gets the leaders of the SMPSO algorithm
	 */
	public SolutionSet getLeader() {
		return leaders_;
	}  // getLeader   

	public ChartPanel getChartPanelExternalArchive() {
		return plot_manager.getChartPanelExternalArchive();
	}

	public void setChartPanelExternalArchive (ChartPanel chartPanelExternalArchive) {
		this.plot_manager.setChartPanelExternalArchive(chartPanelExternalArchive);
	}

	public ChartPanel getChartPanelSwarm() {
		return plot_manager.getChartPanelSwarm();
	}

	public void getChartPanelSwarm(ChartPanel chartPanelSwarm) {
		this.plot_manager.setChartPanelSwarm(chartPanelSwarm);
	}


} // SMPSO
